package model

import (
	"errors"
	"fmt"
	stdLog "log"
	"path/filepath"
	"strings"
	"time"

	"github.com/panjf2000/ants"

	"gitee.com/goframe/lt-collector/libs/abio"
	"gitee.com/goframe/lt-collector/libs/orma"
	"gitee.com/goframe/lt-collector/syslt"
)

const (
	_ = iota

	CCodeExists     //号码已存在
	CCodeIng        //开奖中
	CCodeSaveFailed //数据保存失败
	CCodeOK         //数据采集完成
)

var (
	CCodeMap = map[int]string{
		CCodeExists:     "开奖号码已存在",
		CCodeIng:        "开奖中...",
		CCodeSaveFailed: "数据保存失败",
		CCodeOK:         "数据采集完成",
	}

	logOut = abio.NewAbFile(filepath.Join(abio.SelfDir(), "logs", "collector", "log.log"), nil)
	log    = stdLog.New(logOut, strings.ToUpper("[collector] "), stdLog.LstdFlags)
	//sLog   = stdLog.New(os.Stdout, "", stdLog.LstdFlags)

	normalTimeDuration = time.Second * 3
)

type (
	collector struct {
		lt *GameLotteries

		openData *OpenData
		errCode  int
		sleep    time.Duration

		stop chan bool
	}
)

func Collector() {
	lts := make([]*GameLotteries, 0)
	err := orma.GetEngine().Where("`enable`=1").Find(&lts)
	if err != nil {
		return
	}
	antsPool, _ := ants.NewPool(len(lts))
	for _, lt := range lts {
		bean := &collector{
			lt:   lt,
			stop: make(chan bool, 1),
		}
		_ = antsPool.Submit(bean.run)
	}
}

func (c *collector) run() {
	if c.sleep == 0 {
		c.sleep = normalTimeDuration
	}
	tm := time.NewTimer(normalTimeDuration)
	var err error
	defer func() {
		tm.Stop()
		close(c.stop)
	}()
	for {
		c.sleep = normalTimeDuration
		select {
		case <-tm.C:
			if err = c.fillOpenData(); err != nil {
				log.Println(err.Error())
			}
		case <-c.stop:
			return
		}
		tm.Reset(c.sleep)
	}
}

var (
	creators = map[int]syslt.Creator{
		TK3: syslt.NewK3(30),
	}
)

func (c *collector) getSysData() (data RequestData, err error) {
	creator, has := creators[c.lt.TypeID]
	if !has {
		err = errors.New("未绑定系统彩号码创建者")
		c.stop <- true
		return
	}
	gt := &GameSysTmp{Gid: c.lt.ID}
	sl := syslt.NewSysLt(gt, creator)
	var code string
	code, err = sl.GetCode()
	if err != nil {
		return
	}
	gt.OpenCode = code
	data = gt
	if len(data.GetOpenCode()) == 0 {
		c.errCode = CCodeIng
		err = fmt.Errorf(CCodeMap[c.errCode])
	}
	c.openData.OpenCode = code
	return
}

func (c *collector) fillOpenData() (err error) {
	if err = c.setOpenData(); err != nil {
		return
	}
	var logHead = fmt.Sprintf("[%s]第[%s]期:", c.lt.NameCN, c.openData.Issue)
	defer func() {
		if err != nil {
			err = fmt.Errorf("%s%s;线程休眠%s", logHead, err.Error(), c.sleep.String())
		}
	}()
	var data RequestData
	if (c.lt.CollectSourceIds == nil || len(c.lt.CollectSourceIds) == 0) && !c.lt.IsSys {
		c.stop <- true
		return fmt.Errorf("%s采集资源点未设置", logHead)
	}
	if c.lt.IsSys {
		data, err = c.getSysData()
	} else {
		//add
		if c.errCode == CCodeIng {
			c.sleep = c.openData.OpenTime.Convert2Time().Sub(time.Now())
			if c.sleep <= 0 {
				c.sleep = normalTimeDuration
			}
		}

		for _, sid := range c.lt.CollectSourceIds {
			sc := new(GameCollectSource)
			sc.ID = int64(sid)
			data, err = sc.getRequestData(c.lt.ID)
			if err != nil {
				continue
			}
			c.openData.FromSourceID = sid
			c.openData.OpenCode = data.GetOpenCode()
			break
		}
	}
	if err != nil {
		c.errCode = CCodeIng
		return fmt.Errorf(CCodeMap[c.errCode])
	}
	if err = c.checkRequestData(data); err != nil {
		return
	}
	c.sendCode()
	err = orma.NormalInsert(c.openData.Session().InsertOne(c.openData))
	if err != nil {
		c.errCode = CCodeSaveFailed
	} else {
		c.errCode = CCodeOK
		c.gotCode()
	}
	err = fmt.Errorf("%s:[%s]", CCodeMap[c.errCode], c.openData.OpenCode)
	return
}

func (c *collector) gotCode() {
	c.sleep = c.openData.NextOpenTime.Convert2Time().Sub(time.Now())
	if c.sleep <= 0 {
		c.sleep = normalTimeDuration
	}
}

func (c *collector) beforeSend() {
	c.openData.ID = 0
	c.openData.Created = nil
	c.openData.Updated = nil
	c.openData.Version = 0
	c.openData.OtherData = nil
	c.openData.LotName = c.lt.Name
	c.openData.LotNameCN = c.lt.NameCN
}

func (c *collector) sendCode() {
	//Send openData
	c.beforeSend()
	_ = ModeWebsocket.SendToAll("OpenData", c.openData)
}

func (c *collector) checkRequestData(data RequestData) (err error) {
	switch c.lt.Name {
	case "jlk3", "bjpk10":
		if c.openData.Issue != data.GetOpenIssue() {
			c.errCode = CCodeIng
			return fmt.Errorf(CCodeMap[c.errCode])
		}
	default:
		var openTime = data.GetOpenTime()
		switch {
		case strings.Contains(c.lt.Name, "az"):
			openTime = orma.JSONTime(openTime.Convert2Time().Add((time.Hour*2 + time.Minute*30) * -1))
			fallthrough
		default:
			if (&openTime).Convert2Time().Add(time.Second * 30).Before(c.openData.OpenTime.Convert2Time()) {
				c.errCode = CCodeIng
				return fmt.Errorf(CCodeMap[c.errCode])
			}
		}
	}
	var has bool
	openData := new(OpenData)
	has, err = openData.Session().Where("gid=? and issue=?", c.openData.Gid, c.openData.Issue).Get(openData)
	if err != nil {
		return
	}
	if has {
		c.errCode = CCodeExists
		c.openData = openData
		c.gotCode()
		c.sendCode()
		return fmt.Errorf("%s:[%s]", CCodeMap[c.errCode], c.openData.OpenCode)
	}
	return
}

func (c *collector) setOpenData() (err error) {
	plan := new(GameIssuePlan)
	if err = plan.getByGid(c.lt.ID); err != nil {
		return
	}
	c.openData, err = plan.ParseIssueAndTime()
	return
}
